Projekt Dokumentation

Aufgabe: Vorhersage der Umsätze vom 9.6.2019 bis 30.07.2019

Infos zu den gegebenen Daten

Warengruppen: * 1 = Brot * 2 = Brötchen * 3 = Croissant * 4 = Konditorei * 5 = Kuchen * 6 = Saisonbrot

Saisonbrot muss nicht vorhergesagt werden! Siehe ‘predition_template.csv’.

Wetterdaten: * Mittlerer Bewölkungsgrad am Tag (0 = min, 8 = max) * MIttlere Temperatur in C * Mittlere Windgeschwindigkeit in m/s * Wettercode (http://www.seewetter-kiel.de/seewetter/daten_symbole.htm) * und in der Datei wettercodes.Rda

Vorbereitung & benötigte Libraries laden

remove(list = ls())
# Create list with needed libraries
# Quellen:
#   1. synthpop: https://cran.r-project.org/web/packages/synthpop/vignettes/synthpop.pdf
#   2. 
pkgs <- c("lubridate", "stringr","tidyverse", "readr", 
          "fastDummies", "reticulate", "ggplot2", "Metrics", "VIM", "synthpop", "httr")

# Load each listed library and check if it is installed and install if necessary
for (pkg in pkgs) {
  if (!require(pkg, character.only = TRUE)) {
    install.packages(pkg)
    library(pkg, character.only = TRUE)
  }
}

Vorbereitete Datensätze laden

  • Wetterdaten wurden in “Datenaufbereitung_Wetter.Rmd” vorbereitet
  • Feiertagedaten wurden in “Datenaufbereitung_Feiertage.R” vorbereitet
  • Schulferien wurden in “Datenaufbereitung_Schulferien.R” vorbereitet
  • Umsatzdaten wurden in “Datenaufbereitung_Umsatz.R” vorbereitet
# Lade Daten
load("pj_wetter_dummy.Rda")
pj_wetter <- pj_wetter_dummy
  
load("kiwoDT.Rda")
pj_kiwo <- kiwoDT
  
load("pj_umsatz.Rda")

load("schulferien.Rda")
pj_schulferien <- schulferien

# Erste Betrachtung der Daten
#summary(pj_wetter)
#summary(pj_kiwo)
#summary(pj_umsatz)

allData_dummy

# Monatlichen Umsatzt von Nahrungsmittel Facheinzelhandel in SH (auch Bäckerein) --> Datenaufbereitung
umsatztFachEinzelHandelSH <- read_csv("umsatztFachEinzelHandel.csv")
New names:Rows: 73 Columns: 4── Column specification ──────────────────────────────────────────────
Delimiter: ","
dbl (4): ...1, Jahr, Monat, Umsatz
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
umsatztFachEinzelHandelSH <- select(umsatztFachEinzelHandelSH, "Jahr", "Monat", "Umsatz")
# Create a new column called "Datum"
umsatztFachEinzelHandelSH$Datum <- as.Date(paste(umsatztFachEinzelHandelSH$Jahr, umsatztFachEinzelHandelSH$Monat, "01", sep = "-"), format = "%Y-%m-%d")

# Remove the original Jahr and Monat columns
umsatztFachEinzelHandelSH$Jahr <- NULL
umsatztFachEinzelHandelSH$Monat <- NULL

#grouping the dataframe by year and month
umsatztFachEinzelHandelSH <- umsatztFachEinzelHandelSH %>% 
    group_by(year(Datum), month(Datum))
#selecting the first row of each group
first_row <- umsatztFachEinzelHandelSH %>% 
    slice_head(n=1)
#creating a new data frame with all the days in each month
days <- expand.grid(Jahr=unique(year(umsatztFachEinzelHandelSH$Datum)),Monat=unique(month(umsatztFachEinzelHandelSH$Datum)),Day=1:31)
# converting the above grid to a date format
days$Datum <- as.Date(paste(days$Jahr, days$Monat, days$Day, sep = "-"), format = "%Y-%m-%d")
# Removing the unnecessary columns from days dataframe
days$Jahr<-NULL
days$Monat<-NULL
days$Day<-NULL
#merging the two dataframe
umsatztFachEinzelHandelSH<-left_join(days,first_row,by=c("Datum"))
# Remove the original Jahr and Monat columns
umsatztFachEinzelHandelSH$`year(Datum)` <- NULL
umsatztFachEinzelHandelSH$`month(Datum)` <- NULL
umsatztFachEinzelHandelSH <- umsatztFachEinzelHandelSH %>% 
    filter(Datum >= as.Date("2013-07-01"))
umsatztFachEinzelHandelSH <- umsatztFachEinzelHandelSH %>% 
    filter(Datum < as.Date("2019-07-31"))

umsatztFachEinzelHandelSH <- umsatztFachEinzelHandelSH %>%  
  hotdeck(variable = c("Umsatz"), ord_var = "Datum")

ggplot(umsatztFachEinzelHandelSH) +
  geom_point(aes(x = Datum, y = Umsatz, color = Umsatz_imp))


umsatztFachEinzelHandelSH$Umsatz_imp <- NULL

# Merge erstellt automatisch die Schnittmenge
# Der Zusatz all.x = TRUE sorgt dafür, dass keine Zeilen (basierend auf Datensatz x) weggelöscht werden
# Wetterdaten nach Datum hinzufügen
pj_umsatz_wetter <- merge(pj_umsatz, pj_wetter, by="Datum", all.x = TRUE)

# Schulferien nach Datum hinzufügen
pj_umsatz_wetter_ferien <- merge(pj_umsatz_wetter, pj_schulferien, by="Datum", all.x = TRUE)

# KiWo nach Datum hinzufügen
allData <- merge(pj_umsatz_wetter_ferien, pj_kiwo, by="Datum", all.x = TRUE)

allData <- merge(allData, umsatztFachEinzelHandelSH, by="Datum", all.x = TRUE)

# auf fehlende Werte überprüfen:
allData_na <- allData %>%
  aggr(combined=TRUE, numbers=TRUE)
Warning: not enough horizontal space to display frequencies

# Imputation Temperatur und Windstaerke
# Aktuell: "Datenspende" vom Wert vom Vortag
# ZIEL: Mittelwert aus Temperatur von Vortag und Tag danach -> Armando! :)
allData <- allData %>%  
  hotdeck(variable = c("Temperatur", "Windstaerke"),
          ord_var = "Datum")

#imputierte Werte graphisch überprüfen:
ggplot(allData) +
  geom_point(aes(x = Datum, y = Temperatur, color = Temperatur_imp))

ggplot(allData) +
  geom_point(aes(x = Datum, y = Windstaerke, color = Windstaerke_imp))


# NA Wettercodes zu 0, da Spalte WC_NA angibt, wo Wettercodes gefehlt haben
# Spalten 12 -24

# das gleiche gilt bei der Bewölkung
# Spalten 26 - 29

# weitere NA mit 0 füllen, dort wo es Sinn ergibt  

allData <- allData %>%
    mutate_at(c(12:34), ~replace(., is.na(.), 0))

# generating synthetic data 
synthpop_allData <- syn(allData)[["syn"]]

Variable(s): Wochentag have been changed for synthesis from character to factor.
Warning: In your synthesis there are numeric variables with 5 or fewer levels: WC_Bewölkung_nicht_beobachtet, WC_Bewölkung_zunehmend, WC_Dunst_Staub, WC_Ereignisse_letzte_h, WC_Gewitter, WC_Nebel_Eisnebel, WC_Regen, WC_Schnee, WC_Sprühregen, WC_Trockenereignisse, WC_NA, Bewoelkungsgrad_gering, Bewoelkungsgrad_keine, Bewoelkungsgrad_mittel, Bewoelkungsgrad_stark, Bewoelkungsgrad_NA, Schulferien, KielerWoche, Temperatur_imp.
Consider changing them to factors. You can do it using parameter 'minnumlevels'.

Variable(s): WC_Bewölkung_abnehmend, WC_Bewölkung_gleichbleibend, WC_Schauer numeric but with only 1 or fewer distinct values turned into factor(s) for synthesis.

Variable WC_Bewölkung_abnehmend has only one value so its method has been changed to "constant".
Variable WC_Bewölkung_abnehmend removed as predictor because only one value.
Variable WC_Bewölkung_gleichbleibend has only one value so its method has been changed to "constant".
Variable WC_Bewölkung_gleichbleibend removed as predictor because only one value.
Variable WC_Schauer has only one value so its method has been changed to "constant".
Variable WC_Schauer removed as predictor because only one value.
Variables Temperatur_imp, Windstaerke_imp are collinear. Variables later in 'visit.sequence'
are derived from Temperatur_imp.


Synthesis
-----------
 Datum Brot Brötchen Croissant Konditorei Kuchen Saisonbrot Wochentag Konditorei_imp Windstaerke
 Temperatur WC_Bewölkung_abnehmend WC_Bewölkung_gleichbleibend WC_Bewölkung_nicht_beobachtet WC_Bewölkung_zunehmend WC_Dunst_Staub WC_Ereignisse_letzte_h WC_Gewitter WC_Nebel_Eisnebel WC_Regen
 WC_Schauer WC_Schnee WC_Sprühregen WC_Trockenereignisse WC_NA Bewoelkungsgrad_gering Bewoelkungsgrad_keine Bewoelkungsgrad_mittel Bewoelkungsgrad_stark Bewoelkungsgrad_NA
 Schulferien KielerWoche Umsatz Temperatur_imp Windstaerke_imp
# dummy coding der Wochentage
allData_dummy <- dummy_cols(allData, select_columns = "Wochentag")
synthpop_allData_dummy <- dummy_cols(synthpop_allData, select_columns = "Wochentag")

allData_dummy$year <- year(allData_dummy$Datum)
allData_dummy$month <- month(allData_dummy$Datum)
allData_dummy$day <- day(allData_dummy$Datum)
synthpop_allData_dummy$year <- year(synthpop_allData_dummy$Datum)
synthpop_allData_dummy$month <- month(synthpop_allData_dummy$Datum)
synthpop_allData_dummy$day <- day(synthpop_allData_dummy$Datum)

allData_dummy$Datum <- NULL
synthpop_allData_dummy$Datum <- NULL

#summary(allData_dummy)
save(allData_dummy, file="projectData_dummy.Rda")

Testdatensatz

# Erstelle einen leeren Dataframe mit einer Spalte für das Datum
testDatenSatz <- data.frame(Datum = character())

# Erstelle eine Sequenz von Daten im angegebenen Zeitraum
datum_sequenz <- seq(from = as.Date("2019-06-09"),
                     to = as.Date("2019-07-30"),
                     by = "days")

# Füge die Daten der Sequenz dem Dataframe hinzu
sBrot <- select(pj_umsatz, "Datum", "Saisonbrot")
testDatenSatz <- rbind(testDatenSatz, data.frame(Datum = datum_sequenz))
testDatenSatz$Wochentag <- weekdays(testDatenSatz$Datum)
testDatenSatz <- merge(testDatenSatz, pj_wetter, by="Datum", all.x = TRUE)
testDatenSatz <- merge(testDatenSatz, pj_schulferien, by="Datum", all.x = TRUE)
testDatenSatz <- merge(testDatenSatz, pj_kiwo, by="Datum", all.x = TRUE)
testDatenSatz <- merge(testDatenSatz, sBrot, by="Datum", all.x = TRUE)
testDatenSatz <- merge(testDatenSatz, umsatztFachEinzelHandelSH, by="Datum", all.x = TRUE)

testDatenSatz <- testDatenSatz %>% 
  hotdeck(variable = c("Temperatur", "Windstaerke"),
          ord_var = "Datum")

#imputierte Werte von testDatenSatz graphisch überprüfen:
ggplot(testDatenSatz) +
  geom_point(aes(x = Datum, y = Temperatur, color = Temperatur_imp))

ggplot(testDatenSatz) +
  geom_point(aes(x = Datum, y = Windstaerke, color = Windstaerke_imp))


testDatenSatz <- testDatenSatz %>%
    mutate_at(c(4:26), ~replace(., is.na(.), 0))

# dummy coding der Wochentage
testDatenSatz <- dummy_cols(testDatenSatz, select_columns = "Wochentag")

testDatenSatz$year <- year(testDatenSatz$Datum)
testDatenSatz$month <- month(testDatenSatz$Datum)
testDatenSatz$day <- day(testDatenSatz$Datum)

testDatenSatz$Datum <- NULL

summary(testDatenSatz)
  Wochentag          Windstaerke      Temperatur   
 Length:52          Min.   :3.000   Min.   :14.46  
 Class :character   1st Qu.:5.000   1st Qu.:16.93  
 Mode  :character   Median :6.000   Median :19.59  
                    Mean   :5.788   Mean   :20.41  
                    3rd Qu.:7.000   3rd Qu.:23.40  
                    Max.   :9.000   Max.   :29.73  
 WC_Bewölkung_abnehmend WC_Bewölkung_gleichbleibend
 Min.   :0              Min.   :0                  
 1st Qu.:0              1st Qu.:0                  
 Median :0              Median :0                  
 Mean   :0              Mean   :0                  
 3rd Qu.:0              3rd Qu.:0                  
 Max.   :0              Max.   :0                  
 WC_Bewölkung_nicht_beobachtet WC_Bewölkung_zunehmend
 Min.   :0.0000                Min.   :0             
 1st Qu.:0.0000                1st Qu.:0             
 Median :0.0000                Median :0             
 Mean   :0.1538                Mean   :0             
 3rd Qu.:0.0000                3rd Qu.:0             
 Max.   :1.0000                Max.   :0             
 WC_Dunst_Staub   WC_Ereignisse_letzte_h  WC_Gewitter    
 Min.   :0.0000   Min.   :0.0000         Min.   :0.0000  
 1st Qu.:0.0000   1st Qu.:0.0000         1st Qu.:0.0000  
 Median :0.0000   Median :0.0000         Median :0.0000  
 Mean   :0.1346   Mean   :0.1538         Mean   :0.1154  
 3rd Qu.:0.0000   3rd Qu.:0.0000         3rd Qu.:0.0000  
 Max.   :1.0000   Max.   :1.0000         Max.   :1.0000  
 WC_Nebel_Eisnebel    WC_Regen        WC_Schauer   WC_Schnee
 Min.   :0         Min.   :0.0000   Min.   :0    Min.   :0  
 1st Qu.:0         1st Qu.:0.0000   1st Qu.:0    1st Qu.:0  
 Median :0         Median :0.0000   Median :0    Median :0  
 Mean   :0         Mean   :0.1731   Mean   :0    Mean   :0  
 3rd Qu.:0         3rd Qu.:0.0000   3rd Qu.:0    3rd Qu.:0  
 Max.   :0         Max.   :1.0000   Max.   :0    Max.   :0  
 WC_Sprühregen     WC_Trockenereignisse     WC_NA       
 Min.   :0.00000   Min.   :0.00000      Min.   :0.0000  
 1st Qu.:0.00000   1st Qu.:0.00000      1st Qu.:0.0000  
 Median :0.00000   Median :0.00000      Median :0.0000  
 Mean   :0.01923   Mean   :0.01923      Mean   :0.2308  
 3rd Qu.:0.00000   3rd Qu.:0.00000      3rd Qu.:0.0000  
 Max.   :1.00000   Max.   :1.00000      Max.   :1.0000  
 Bewoelkungsgrad_gering Bewoelkungsgrad_keine Bewoelkungsgrad_mittel
 Min.   :0.00000        Min.   :0.0000        Min.   :0.0000        
 1st Qu.:0.00000        1st Qu.:0.0000        1st Qu.:0.0000        
 Median :0.00000        Median :0.0000        Median :0.0000        
 Mean   :0.05769        Mean   :0.1346        Mean   :0.4615        
 3rd Qu.:0.00000        3rd Qu.:0.0000        3rd Qu.:1.0000        
 Max.   :1.00000        Max.   :1.0000        Max.   :1.0000        
 Bewoelkungsgrad_stark Bewoelkungsgrad_NA  Schulferien    
 Min.   :0.0000        Min.   :0          Min.   :0.0000  
 1st Qu.:0.0000        1st Qu.:0          1st Qu.:0.0000  
 Median :0.0000        Median :0          Median :1.0000  
 Mean   :0.3462        Mean   :0          Mean   :0.5769  
 3rd Qu.:1.0000        3rd Qu.:0          3rd Qu.:1.0000  
 Max.   :1.0000        Max.   :0          Max.   :1.0000  
  KielerWoche       Saisonbrot     Umsatz      Temperatur_imp 
 Min.   :0.0000   Min.   :0    Min.   :118.5   Mode :logical  
 1st Qu.:0.0000   1st Qu.:0    1st Qu.:118.5   FALSE:52       
 Median :0.0000   Median :0    Median :122.4                  
 Mean   :0.1731   Mean   :0    Mean   :120.8                  
 3rd Qu.:0.0000   3rd Qu.:0    3rd Qu.:122.4                  
 Max.   :1.0000   Max.   :0    Max.   :122.4                  
 Windstaerke_imp Wochentag_Friday Wochentag_Monday Wochentag_Saturday
 Mode :logical   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000    
 FALSE:52        1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.0000    
                 Median :0.0000   Median :0.0000   Median :0.0000    
                 Mean   :0.1346   Mean   :0.1538   Mean   :0.1346    
                 3rd Qu.:0.0000   3rd Qu.:0.0000   3rd Qu.:0.0000    
                 Max.   :1.0000   Max.   :1.0000   Max.   :1.0000    
 Wochentag_Sunday Wochentag_Thursday Wochentag_Tuesday
 Min.   :0.0000   Min.   :0.0000     Min.   :0.0000   
 1st Qu.:0.0000   1st Qu.:0.0000     1st Qu.:0.0000   
 Median :0.0000   Median :0.0000     Median :0.0000   
 Mean   :0.1538   Mean   :0.1346     Mean   :0.1538   
 3rd Qu.:0.0000   3rd Qu.:0.0000     3rd Qu.:0.0000   
 Max.   :1.0000   Max.   :1.0000     Max.   :1.0000   
 Wochentag_Wednesday      year          month            day       
 Min.   :0.0000      Min.   :2019   Min.   :6.000   Min.   : 1.00  
 1st Qu.:0.0000      1st Qu.:2019   1st Qu.:6.000   1st Qu.:11.00  
 Median :0.0000      Median :2019   Median :7.000   Median :17.50  
 Mean   :0.1346      Mean   :2019   Mean   :6.577   Mean   :17.19  
 3rd Qu.:0.0000      3rd Qu.:2019   3rd Qu.:7.000   3rd Qu.:24.00  
 Max.   :1.0000      Max.   :2019   Max.   :7.000   Max.   :30.00  
save(testDatenSatz, file="Datenaufbereitung_Testdaten.Rda")

Features & Labels

features <- c("day",                           "month",                         "year",
              "Windstaerke",                   "Temperatur",                    "WC_Bewölkung_abnehmend",
              "WC_Bewölkung_gleichbleibend",   "WC_Bewölkung_nicht_beobachtet", "WC_Bewölkung_zunehmend",
              "WC_Dunst_Staub",                "WC_Ereignisse_letzte_h",        "WC_Gewitter",
              "WC_Nebel_Eisnebel",             "WC_Regen",                      "WC_Schauer",
              "WC_Schnee",                     "WC_Sprühregen",                 "WC_Trockenereignisse",
              "WC_NA",                         "Bewoelkungsgrad_gering",        "Bewoelkungsgrad_keine",
              "Bewoelkungsgrad_mittel",        "Bewoelkungsgrad_stark",         "Bewoelkungsgrad_NA",
              "Schulferien",                   "KielerWoche",                   "Wochentag_Tuesday",
              "Wochentag_Thursday",            "Saisonbrot",                    "Umsatz",
              "Wochentag_Friday",              "Wochentag_Wednesday",           "Wochentag_Monday",
              "Wochentag_Saturday",            "Wochentag_Sunday"
              )

labels <- c("Brot", "Brötchen", "Croissant", "Konditorei", "Kuchen")

Selection of Training, Validation and Test Data

# Setting the random counter to a fixed value, so the random initialization stays the same (the random split is always the same)
set.seed(1)

assignment <- sample(1:2, size = nrow(allData_dummy), prob = c(.8, .2), replace = TRUE)
allData_dummy2 <- rbind(allData_dummy[assignment == 1,], synthpop_allData_dummy)

# Create training, validation and test data for the features and the labels
training_features <- allData_dummy2[,features]#[assignment == 1, features]    
training_labels <- allData_dummy2[,labels]#[assignment == 1, labels]    
training_labels <- as.data.frame(training_labels)

validation_features <- allData_dummy[assignment == 2, features]  
validation_labels <- allData_dummy[assignment == 2, labels]  
validation_labels <- as.data.frame(validation_labels)

testing_features <- testDatenSatz %>% select(all_of(features))

#are there any missing values?
table(is.na(training_features))

 FALSE 
133455 
table(is.na(validation_features))

FALSE 
15155 
table(is.na(testing_features))

FALSE 
 1820 
#summary(allData_dummy)

Modell aufstellen in Python

reticulate::repl_python()
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import InputLayer, Dense, BatchNormalization, Dropout
from tensorflow.keras.optimizers import Adam

# The argument "input_shape" for the definition of the input layer must include 
# the number of input variables (features) used for the model. 
# To automatically calculate this number we use the function `r.training_features.keys()`, 
# which returns the list of variable names of the dataframe `training_features`.
# Then, the funtion `len()` returns the length of this list of variable names 
# (i.e. the number of variables in the input)

model = Sequential([
  InputLayer(input_shape = (len(r.training_features.keys()), )),
  BatchNormalization(),
  Dense(len(r.training_features.keys()), activation = 'swish'),
  Dropout(0.2),
  Dense(len(r.training_features.keys()), activation = 'swish'),
  Dropout(0.2),
  Dense(len(r.training_features.keys()), activation = 'swish'),
  Dropout(0.2),
  Dense(len(r.training_features.keys()), activation = 'swish'),
  Dense(5)
])

# Ausgabe einer ZUsammenfassung zur Form des MOdells, das geschätzt wird (nicht notwendig)
#model.summary()

Schätzung de neuronalen Netzes

# definition of the loss function and the optimazation function with hyperparameters
model.compile(loss="mape", optimizer=Adam(learning_rate=0.001))

#Schätzung des Modells
history = model.fit(r.training_features, r.training_labels, epochs = 750,
                    validation_data = (r.validation_features, r.validation_labels), verbose = 0)

model.save("python_model.h5")

graphische Ausgabe der Modelloptimierung

quit
# Graphische Ausgabe der Modelloptimierung

#create data
data <- data.frame(val_loss = unlist(py$history$history$val_loss),
                   loss = unlist(py$history$history$loss))

ggplot(data[-(1:10), ])+
  geom_line(aes(x = 1:length(val_loss), y = val_loss, colour = "Validation Loss")) +
  geom_line(aes(x = 1:length(loss), y = loss, colour = "Training Loss")) +
  scale_colour_manual(values = c("Training Loss"="blue", "Validation Loss" = "red")) +
  labs(title = "Loss Function Values During Optimazation") +
  xlab("Iteration Number") +
  ylab("Loss")

Auswertung der Schätzergebnisse

# Schätzung der (normierten) Preise für die Trainings- und Testdaten
training_predictions <- py$model$predict(training_features)

  1/120 [..............................] - ETA: 5s
120/120 [==============================] - 0s 303us/step
validation_predictions <- py$model$predict(validation_features)

 1/14 [=>............................] - ETA: 0s
14/14 [==============================] - 0s 278us/step
testing_predictions <- py$model$predict(testing_features)

1/2 [==============>...............] - ETA: 0s
2/2 [==============================] - 0s 570us/step
# Vergleich der Gütekriterien für die Traingings- und Testdaten
a <- format(mape(training_labels[,1], training_predictions[,1])*100, digits=3, nsmall=2)
b <- format(mape(training_labels[,2], training_predictions[,2])*100, digits=3, nsmall=2)
c <- format(mape(training_labels[,3], training_predictions[,3])*100, digits=3, nsmall=2)
d <- format(mape(training_labels[,4], training_predictions[,4])*100, digits=3, nsmall=2)
e <- format(mape(training_labels[,5], training_predictions[,5])*100, digits=3, nsmall=2)

cat(paste0("\nMAPE on the Training Data1:\t", a))

MAPE on the Training Data1: 17.20
cat(paste0("\nMAPE on the Training Data2:\t", b))

MAPE on the Training Data2: 10.30
cat(paste0("\nMAPE on the Training Data3:\t", c))

MAPE on the Training Data3: 15.14
cat(paste0("\nMAPE on the Training Data4:\t", d))

MAPE on the Training Data4: 19.35
cat(paste0("\nMAPE on the Training Data5:\t", e, "\n"))

MAPE on the Training Data5: 12.59
g <- format(mape(validation_labels[,1], validation_predictions[,1])*100, digits=3, nsmall=2)
h <- format(mape(validation_labels[,2], validation_predictions[,2])*100, digits=3, nsmall=2)
i <- format(mape(validation_labels[,3], validation_predictions[,3])*100, digits=3, nsmall=2)
j <- format(mape(validation_labels[,4], validation_predictions[,4])*100, digits=3, nsmall=2)
k <- format(mape(validation_labels[,5], validation_predictions[,5])*100, digits=3, nsmall=2)
  
cat(paste0("\nMAPE on the Validation Data1:\t", g))

MAPE on the Validation Data1:   18.65
cat(paste0("\nMAPE on the Validation Data2:\t", h))

MAPE on the Validation Data2:   11.41
cat(paste0("\nMAPE on the Validation Data3:\t", i))

MAPE on the Validation Data3:   17.59
cat(paste0("\nMAPE on the Validation Data4:\t", j))

MAPE on the Validation Data4:   19.88
cat(paste0("\nMAPE on the Validation Data5:\t", k, "\n"))

MAPE on the Validation Data5:   13.73
# Mean of Training and Validation Data MAPE
meanT <- c(as.double(a), as.double(b), as.double(c), as.double(d), as.double(e)) 
meanV <- c(as.double(g), as.double(h), as.double(i), as.double(j), as.double(k)) 

cat(paste0("\nMean Training MAPE: ", mean(meanT), "\n"))

Mean Training MAPE: 14.916
cat(paste0("Mean Validation MAPE: ", mean(meanV), "\n"))
Mean Validation MAPE: 16.252

Grafischer vergleich der vorhergesagten & tatsächlicher Preise für die Trainings- und Validierungsdaten

data_train <- data.frame(prediction = training_predictions[,1], actual = training_labels[,1])
data_val <- data.frame(prediction = validation_predictions[,1], actual = validation_labels[,1])
data_test <- data.frame(prediction = testing_predictions[,1])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train[]) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 1") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val[,]) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 1") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 1") +
  xlab("Case Number") +
  ylab("Price in EUR") 


#------------------------- 2 -------------------------#

data_train2 <- data.frame(prediction = training_predictions[,2], actual = training_labels[,2])
data_val2 <- data.frame(prediction = validation_predictions[,2], actual = validation_labels[,2])
data_test2 <- data.frame(prediction = testing_predictions[,2])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train2) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 2") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val2) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 2") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test2) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 2") +
  xlab("Case Number") +
  ylab("Price in EUR") 


#------------------------- 3 -------------------------#

data_train3 <- data.frame(prediction = training_predictions[,3], actual = training_labels[,3])
data_val3 <- data.frame(prediction = validation_predictions[,3], actual = validation_labels[,3])
data_test3 <- data.frame(prediction = testing_predictions[,3])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train3) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 3") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val3) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 3") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test3) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 3") +
  xlab("Case Number") +
  ylab("Price in EUR") 



#------------------------- 4 -------------------------#

data_train4 <- data.frame(prediction = training_predictions[,4], actual = training_labels[,4])
data_val4 <- data.frame(prediction = validation_predictions[,4], actual = validation_labels[,4])
data_test4 <- data.frame(prediction = testing_predictions[,4])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train4) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 4") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val4) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 4") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test4) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 4") +
  xlab("Case Number") +
  ylab("Price in EUR") 


#------------------------- 5 -------------------------#

data_train5 <- data.frame(prediction = training_predictions[,5], actual = training_labels[,5])
data_val5 <- data.frame(prediction = validation_predictions[,5], actual = validation_labels[,5])
data_test5 <- data.frame(prediction = testing_predictions[,5])

# Plot der Ergebnisse der Trainingsdaten
ggplot(data_train5) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Training Data 5") +
  xlab("Case Number") +
  ylab("Price in EUR") 


# Plot der Ergebnisse der Validierungsdaten
ggplot(data_val5) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  geom_line( aes(x=1:length(actual), y=actual, colour = "Actual Values" )) +
  scale_colour_manual( values = c("Predicted Values"="blue", "Actual Values"="red") ) +
  labs(title="Predicted and Actual Values for the Validation Data 5") +
  xlab("Case Number") +
  ylab("Price in EUR")


# Plot der Ergebnisse der Testdaten
ggplot(data_test5) +
  geom_line( aes(x=1:length(prediction), y=prediction, colour = "Predicted Values" )) +
  labs(title="Prediction for the Test Data 5") +
  xlab("Case Number") +
  ylab("Price in EUR") 

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBtYXJrZG93bjogCiAgICB3cmFwOiA3MgotLS0KCiMgUHJvamVrdCBEb2t1bWVudGF0aW9uCgpBdWZnYWJlOiBWb3JoZXJzYWdlIGRlciBVbXPDpHR6ZSB2b20gOS42LjIwMTkgYmlzIDMwLjA3LjIwMTkKCiMjIyBJbmZvcyB6dSBkZW4gZ2VnZWJlbmVuIERhdGVuCgpXYXJlbmdydXBwZW46IFwqIDEgPSBCcm90IFwqIDIgPSBCcsO2dGNoZW4gXCogMyA9IENyb2lzc2FudCBcKiA0ID0KS29uZGl0b3JlaSBcKiA1ID0gS3VjaGVuIFwqIDYgPSBTYWlzb25icm90CgojIyMgU2Fpc29uYnJvdCBtdXNzIG5pY2h0IHZvcmhlcmdlc2FndCB3ZXJkZW4hIFNpZWhlICdwcmVkaXRpb25fdGVtcGxhdGUuY3N2Jy4KCldldHRlcmRhdGVuOiBcKiBNaXR0bGVyZXIgQmV3w7Zsa3VuZ3NncmFkIGFtIFRhZyAoMCA9IG1pbiwgOCA9IG1heCkgXCoKTUl0dGxlcmUgVGVtcGVyYXR1ciBpbiBDIFwqIE1pdHRsZXJlIFdpbmRnZXNjaHdpbmRpZ2tlaXQgaW4gbS9zIFwqCldldHRlcmNvZGUgKDxodHRwOi8vd3d3LnNlZXdldHRlci1raWVsLmRlL3NlZXdldHRlci9kYXRlbl9zeW1ib2xlLmh0bT4pClwqIHVuZCBpbiBkZXIgRGF0ZWkgd2V0dGVyY29kZXMuUmRhCgojIyMgVm9yYmVyZWl0dW5nICYgYmVuw7Z0aWd0ZSBMaWJyYXJpZXMgbGFkZW4KCmBgYHtyfQpyZW1vdmUobGlzdCA9IGxzKCkpCiMgQ3JlYXRlIGxpc3Qgd2l0aCBuZWVkZWQgbGlicmFyaWVzCiMgUXVlbGxlbjoKIyAgIDEuIHN5bnRocG9wOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvc3ludGhwb3AvdmlnbmV0dGVzL3N5bnRocG9wLnBkZgojICAgMi4gCnBrZ3MgPC0gYygibHVicmlkYXRlIiwgInN0cmluZ3IiLCJ0aWR5dmVyc2UiLCAicmVhZHIiLCAKICAgICAgICAgICJmYXN0RHVtbWllcyIsICJyZXRpY3VsYXRlIiwgImdncGxvdDIiLCAiTWV0cmljcyIsICJWSU0iLCAic3ludGhwb3AiLCAiaHR0ciIpCgojIExvYWQgZWFjaCBsaXN0ZWQgbGlicmFyeSBhbmQgY2hlY2sgaWYgaXQgaXMgaW5zdGFsbGVkIGFuZCBpbnN0YWxsIGlmIG5lY2Vzc2FyeQpmb3IgKHBrZyBpbiBwa2dzKSB7CiAgaWYgKCFyZXF1aXJlKHBrZywgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcyhwa2cpCiAgICBsaWJyYXJ5KHBrZywgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQogIH0KfQpgYGAKCiMjIyBWb3JiZXJlaXRldGUgRGF0ZW5zw6R0emUgbGFkZW4KCi0gICBXZXR0ZXJkYXRlbiB3dXJkZW4gaW4gIkRhdGVuYXVmYmVyZWl0dW5nX1dldHRlci5SbWQiIHZvcmJlcmVpdGV0Ci0gICBGZWllcnRhZ2VkYXRlbiB3dXJkZW4gaW4gIkRhdGVuYXVmYmVyZWl0dW5nX0ZlaWVydGFnZS5SIiB2b3JiZXJlaXRldAotICAgU2NodWxmZXJpZW4gd3VyZGVuIGluICJEYXRlbmF1ZmJlcmVpdHVuZ19TY2h1bGZlcmllbi5SIiB2b3JiZXJlaXRldAotICAgVW1zYXR6ZGF0ZW4gd3VyZGVuIGluICJEYXRlbmF1ZmJlcmVpdHVuZ19VbXNhdHouUiIgdm9yYmVyZWl0ZXQKCmBgYHtyfQojIExhZGUgRGF0ZW4KbG9hZCgicGpfd2V0dGVyX2R1bW15LlJkYSIpCnBqX3dldHRlciA8LSBwal93ZXR0ZXJfZHVtbXkKICAKbG9hZCgia2l3b0RULlJkYSIpCnBqX2tpd28gPC0ga2l3b0RUCiAgCmxvYWQoInBqX3Vtc2F0ei5SZGEiKQoKbG9hZCgic2NodWxmZXJpZW4uUmRhIikKcGpfc2NodWxmZXJpZW4gPC0gc2NodWxmZXJpZW4KCiMgRXJzdGUgQmV0cmFjaHR1bmcgZGVyIERhdGVuCiNzdW1tYXJ5KHBqX3dldHRlcikKI3N1bW1hcnkocGpfa2l3bykKI3N1bW1hcnkocGpfdW1zYXR6KQpgYGAKCiMjIyBhbGxEYXRhX2R1bW15CgpgYGB7cn0KIyBNb25hdGxpY2hlbiBVbXNhdHp0IHZvbiBOYWhydW5nc21pdHRlbCBGYWNoZWluemVsaGFuZGVsIGluIFNIIChhdWNoIELDpGNrZXJlaW4pIC0tPiBEYXRlbmF1ZmJlcmVpdHVuZwp1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIIDwtIHJlYWRfY3N2KCJ1bXNhdHp0RmFjaEVpbnplbEhhbmRlbC5jc3YiKQp1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIIDwtIHNlbGVjdCh1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNILCAiSmFociIsICJNb25hdCIsICJVbXNhdHoiKQojIENyZWF0ZSBhIG5ldyBjb2x1bW4gY2FsbGVkICJEYXR1bSIKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCREYXR1bSA8LSBhcy5EYXRlKHBhc3RlKHVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gkSmFociwgdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCRNb25hdCwgIjAxIiwgc2VwID0gIi0iKSwgZm9ybWF0ID0gIiVZLSVtLSVkIikKCiMgUmVtb3ZlIHRoZSBvcmlnaW5hbCBKYWhyIGFuZCBNb25hdCBjb2x1bW5zCnVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gkSmFociA8LSBOVUxMCnVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gkTW9uYXQgPC0gTlVMTAoKI2dyb3VwaW5nIHRoZSBkYXRhZnJhbWUgYnkgeWVhciBhbmQgbW9udGgKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCA8LSB1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIICU+JSAKICAgIGdyb3VwX2J5KHllYXIoRGF0dW0pLCBtb250aChEYXR1bSkpCiNzZWxlY3RpbmcgdGhlIGZpcnN0IHJvdyBvZiBlYWNoIGdyb3VwCmZpcnN0X3JvdyA8LSB1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIICU+JSAKICAgIHNsaWNlX2hlYWQobj0xKQojY3JlYXRpbmcgYSBuZXcgZGF0YSBmcmFtZSB3aXRoIGFsbCB0aGUgZGF5cyBpbiBlYWNoIG1vbnRoCmRheXMgPC0gZXhwYW5kLmdyaWQoSmFocj11bmlxdWUoeWVhcih1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIJERhdHVtKSksTW9uYXQ9dW5pcXVlKG1vbnRoKHVtc2F0enRGYWNoRWluemVsSGFuZGVsU0gkRGF0dW0pKSxEYXk9MTozMSkKIyBjb252ZXJ0aW5nIHRoZSBhYm92ZSBncmlkIHRvIGEgZGF0ZSBmb3JtYXQKZGF5cyREYXR1bSA8LSBhcy5EYXRlKHBhc3RlKGRheXMkSmFociwgZGF5cyRNb25hdCwgZGF5cyREYXksIHNlcCA9ICItIiksIGZvcm1hdCA9ICIlWS0lbS0lZCIpCiMgUmVtb3ZpbmcgdGhlIHVubmVjZXNzYXJ5IGNvbHVtbnMgZnJvbSBkYXlzIGRhdGFmcmFtZQpkYXlzJEphaHI8LU5VTEwKZGF5cyRNb25hdDwtTlVMTApkYXlzJERheTwtTlVMTAojbWVyZ2luZyB0aGUgdHdvIGRhdGFmcmFtZQp1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIPC1sZWZ0X2pvaW4oZGF5cyxmaXJzdF9yb3csYnk9YygiRGF0dW0iKSkKIyBSZW1vdmUgdGhlIG9yaWdpbmFsIEphaHIgYW5kIE1vbmF0IGNvbHVtbnMKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCRgeWVhcihEYXR1bSlgIDwtIE5VTEwKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCRgbW9udGgoRGF0dW0pYCA8LSBOVUxMCnVtc2F0enRGYWNoRWluemVsSGFuZGVsU0ggPC0gdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCAlPiUgCiAgICBmaWx0ZXIoRGF0dW0gPj0gYXMuRGF0ZSgiMjAxMy0wNy0wMSIpKQp1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIIDwtIHVtc2F0enRGYWNoRWluemVsSGFuZGVsU0ggJT4lIAogICAgZmlsdGVyKERhdHVtIDwgYXMuRGF0ZSgiMjAxOS0wNy0zMSIpKQoKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCA8LSB1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIICU+JSAgCiAgaG90ZGVjayh2YXJpYWJsZSA9IGMoIlVtc2F0eiIpLCBvcmRfdmFyID0gIkRhdHVtIikKCmdncGxvdCh1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNIKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IERhdHVtLCB5ID0gVW1zYXR6LCBjb2xvciA9IFVtc2F0el9pbXApKQoKdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCRVbXNhdHpfaW1wIDwtIE5VTEwKCiMgTWVyZ2UgZXJzdGVsbHQgYXV0b21hdGlzY2ggZGllIFNjaG5pdHRtZW5nZQojIERlciBadXNhdHogYWxsLnggPSBUUlVFIHNvcmd0IGRhZsO8ciwgZGFzcyBrZWluZSBaZWlsZW4gKGJhc2llcmVuZCBhdWYgRGF0ZW5zYXR6IHgpIHdlZ2dlbMO2c2NodCB3ZXJkZW4KIyBXZXR0ZXJkYXRlbiBuYWNoIERhdHVtIGhpbnp1ZsO8Z2VuCnBqX3Vtc2F0el93ZXR0ZXIgPC0gbWVyZ2UocGpfdW1zYXR6LCBwal93ZXR0ZXIsIGJ5PSJEYXR1bSIsIGFsbC54ID0gVFJVRSkKCiMgU2NodWxmZXJpZW4gbmFjaCBEYXR1bSBoaW56dWbDvGdlbgpwal91bXNhdHpfd2V0dGVyX2ZlcmllbiA8LSBtZXJnZShwal91bXNhdHpfd2V0dGVyLCBwal9zY2h1bGZlcmllbiwgYnk9IkRhdHVtIiwgYWxsLnggPSBUUlVFKQoKIyBLaVdvIG5hY2ggRGF0dW0gaGluenVmw7xnZW4KYWxsRGF0YSA8LSBtZXJnZShwal91bXNhdHpfd2V0dGVyX2ZlcmllbiwgcGpfa2l3bywgYnk9IkRhdHVtIiwgYWxsLnggPSBUUlVFKQoKYWxsRGF0YSA8LSBtZXJnZShhbGxEYXRhLCB1bXNhdHp0RmFjaEVpbnplbEhhbmRlbFNILCBieT0iRGF0dW0iLCBhbGwueCA9IFRSVUUpCgojIGF1ZiBmZWhsZW5kZSBXZXJ0ZSDDvGJlcnByw7xmZW46CmFsbERhdGFfbmEgPC0gYWxsRGF0YSAlPiUKICBhZ2dyKGNvbWJpbmVkPVRSVUUsIG51bWJlcnM9VFJVRSkKCiMgSW1wdXRhdGlvbiBUZW1wZXJhdHVyIHVuZCBXaW5kc3RhZXJrZQojIEFrdHVlbGw6ICJEYXRlbnNwZW5kZSIgdm9tIFdlcnQgdm9tIFZvcnRhZwojIFpJRUw6IE1pdHRlbHdlcnQgYXVzIFRlbXBlcmF0dXIgdm9uIFZvcnRhZyB1bmQgVGFnIGRhbmFjaCAtPiBBcm1hbmRvISA6KQphbGxEYXRhIDwtIGFsbERhdGEgJT4lICAKICBob3RkZWNrKHZhcmlhYmxlID0gYygiVGVtcGVyYXR1ciIsICJXaW5kc3RhZXJrZSIpLAogICAgICAgICAgb3JkX3ZhciA9ICJEYXR1bSIpCgojaW1wdXRpZXJ0ZSBXZXJ0ZSBncmFwaGlzY2ggw7xiZXJwcsO8ZmVuOgpnZ3Bsb3QoYWxsRGF0YSkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBEYXR1bSwgeSA9IFRlbXBlcmF0dXIsIGNvbG9yID0gVGVtcGVyYXR1cl9pbXApKQpnZ3Bsb3QoYWxsRGF0YSkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBEYXR1bSwgeSA9IFdpbmRzdGFlcmtlLCBjb2xvciA9IFdpbmRzdGFlcmtlX2ltcCkpCgojIE5BIFdldHRlcmNvZGVzIHp1IDAsIGRhIFNwYWx0ZSBXQ19OQSBhbmdpYnQsIHdvIFdldHRlcmNvZGVzIGdlZmVobHQgaGFiZW4KIyBTcGFsdGVuIDEyIC0yNAoKIyBkYXMgZ2xlaWNoZSBnaWx0IGJlaSBkZXIgQmV3w7Zsa3VuZwojIFNwYWx0ZW4gMjYgLSAyOQoKIyB3ZWl0ZXJlIE5BIG1pdCAwIGbDvGxsZW4sIGRvcnQgd28gZXMgU2lubiBlcmdpYnQgIAoKYWxsRGF0YSA8LSBhbGxEYXRhICU+JQogICAgbXV0YXRlX2F0KGMoMTI6MzQpLCB+cmVwbGFjZSguLCBpcy5uYSguKSwgMCkpCgojIGdlbmVyYXRpbmcgc3ludGhldGljIGRhdGEgCnN5bnRocG9wX2FsbERhdGEgPC0gc3luKGFsbERhdGEpW1sic3luIl1dCgojIGR1bW15IGNvZGluZyBkZXIgV29jaGVudGFnZQphbGxEYXRhX2R1bW15IDwtIGR1bW15X2NvbHMoYWxsRGF0YSwgc2VsZWN0X2NvbHVtbnMgPSAiV29jaGVudGFnIikKc3ludGhwb3BfYWxsRGF0YV9kdW1teSA8LSBkdW1teV9jb2xzKHN5bnRocG9wX2FsbERhdGEsIHNlbGVjdF9jb2x1bW5zID0gIldvY2hlbnRhZyIpCgphbGxEYXRhX2R1bW15JHllYXIgPC0geWVhcihhbGxEYXRhX2R1bW15JERhdHVtKQphbGxEYXRhX2R1bW15JG1vbnRoIDwtIG1vbnRoKGFsbERhdGFfZHVtbXkkRGF0dW0pCmFsbERhdGFfZHVtbXkkZGF5IDwtIGRheShhbGxEYXRhX2R1bW15JERhdHVtKQpzeW50aHBvcF9hbGxEYXRhX2R1bW15JHllYXIgPC0geWVhcihzeW50aHBvcF9hbGxEYXRhX2R1bW15JERhdHVtKQpzeW50aHBvcF9hbGxEYXRhX2R1bW15JG1vbnRoIDwtIG1vbnRoKHN5bnRocG9wX2FsbERhdGFfZHVtbXkkRGF0dW0pCnN5bnRocG9wX2FsbERhdGFfZHVtbXkkZGF5IDwtIGRheShzeW50aHBvcF9hbGxEYXRhX2R1bW15JERhdHVtKQoKYWxsRGF0YV9kdW1teSREYXR1bSA8LSBOVUxMCnN5bnRocG9wX2FsbERhdGFfZHVtbXkkRGF0dW0gPC0gTlVMTAoKI3N1bW1hcnkoYWxsRGF0YV9kdW1teSkKc2F2ZShhbGxEYXRhX2R1bW15LCBmaWxlPSJwcm9qZWN0RGF0YV9kdW1teS5SZGEiKQpgYGAKCiMjIyBUZXN0ZGF0ZW5zYXR6CgpgYGB7cn0KIyBFcnN0ZWxsZSBlaW5lbiBsZWVyZW4gRGF0YWZyYW1lIG1pdCBlaW5lciBTcGFsdGUgZsO8ciBkYXMgRGF0dW0KdGVzdERhdGVuU2F0eiA8LSBkYXRhLmZyYW1lKERhdHVtID0gY2hhcmFjdGVyKCkpCgojIEVyc3RlbGxlIGVpbmUgU2VxdWVueiB2b24gRGF0ZW4gaW0gYW5nZWdlYmVuZW4gWmVpdHJhdW0KZGF0dW1fc2VxdWVueiA8LSBzZXEoZnJvbSA9IGFzLkRhdGUoIjIwMTktMDYtMDkiKSwKICAgICAgICAgICAgICAgICAgICAgdG8gPSBhcy5EYXRlKCIyMDE5LTA3LTMwIiksCiAgICAgICAgICAgICAgICAgICAgIGJ5ID0gImRheXMiKQoKIyBGw7xnZSBkaWUgRGF0ZW4gZGVyIFNlcXVlbnogZGVtIERhdGFmcmFtZSBoaW56dQpzQnJvdCA8LSBzZWxlY3QocGpfdW1zYXR6LCAiRGF0dW0iLCAiU2Fpc29uYnJvdCIpCnRlc3REYXRlblNhdHogPC0gcmJpbmQodGVzdERhdGVuU2F0eiwgZGF0YS5mcmFtZShEYXR1bSA9IGRhdHVtX3NlcXVlbnopKQp0ZXN0RGF0ZW5TYXR6JFdvY2hlbnRhZyA8LSB3ZWVrZGF5cyh0ZXN0RGF0ZW5TYXR6JERhdHVtKQp0ZXN0RGF0ZW5TYXR6IDwtIG1lcmdlKHRlc3REYXRlblNhdHosIHBqX3dldHRlciwgYnk9IkRhdHVtIiwgYWxsLnggPSBUUlVFKQp0ZXN0RGF0ZW5TYXR6IDwtIG1lcmdlKHRlc3REYXRlblNhdHosIHBqX3NjaHVsZmVyaWVuLCBieT0iRGF0dW0iLCBhbGwueCA9IFRSVUUpCnRlc3REYXRlblNhdHogPC0gbWVyZ2UodGVzdERhdGVuU2F0eiwgcGpfa2l3bywgYnk9IkRhdHVtIiwgYWxsLnggPSBUUlVFKQp0ZXN0RGF0ZW5TYXR6IDwtIG1lcmdlKHRlc3REYXRlblNhdHosIHNCcm90LCBieT0iRGF0dW0iLCBhbGwueCA9IFRSVUUpCnRlc3REYXRlblNhdHogPC0gbWVyZ2UodGVzdERhdGVuU2F0eiwgdW1zYXR6dEZhY2hFaW56ZWxIYW5kZWxTSCwgYnk9IkRhdHVtIiwgYWxsLnggPSBUUlVFKQoKdGVzdERhdGVuU2F0eiA8LSB0ZXN0RGF0ZW5TYXR6ICU+JSAKICBob3RkZWNrKHZhcmlhYmxlID0gYygiVGVtcGVyYXR1ciIsICJXaW5kc3RhZXJrZSIpLAogICAgICAgICAgb3JkX3ZhciA9ICJEYXR1bSIpCgojaW1wdXRpZXJ0ZSBXZXJ0ZSB2b24gdGVzdERhdGVuU2F0eiBncmFwaGlzY2ggw7xiZXJwcsO8ZmVuOgpnZ3Bsb3QodGVzdERhdGVuU2F0eikgKwogIGdlb21fcG9pbnQoYWVzKHggPSBEYXR1bSwgeSA9IFRlbXBlcmF0dXIsIGNvbG9yID0gVGVtcGVyYXR1cl9pbXApKQpnZ3Bsb3QodGVzdERhdGVuU2F0eikgKwogIGdlb21fcG9pbnQoYWVzKHggPSBEYXR1bSwgeSA9IFdpbmRzdGFlcmtlLCBjb2xvciA9IFdpbmRzdGFlcmtlX2ltcCkpCgp0ZXN0RGF0ZW5TYXR6IDwtIHRlc3REYXRlblNhdHogJT4lCiAgICBtdXRhdGVfYXQoYyg0OjI2KSwgfnJlcGxhY2UoLiwgaXMubmEoLiksIDApKQoKIyBkdW1teSBjb2RpbmcgZGVyIFdvY2hlbnRhZ2UKdGVzdERhdGVuU2F0eiA8LSBkdW1teV9jb2xzKHRlc3REYXRlblNhdHosIHNlbGVjdF9jb2x1bW5zID0gIldvY2hlbnRhZyIpCgp0ZXN0RGF0ZW5TYXR6JHllYXIgPC0geWVhcih0ZXN0RGF0ZW5TYXR6JERhdHVtKQp0ZXN0RGF0ZW5TYXR6JG1vbnRoIDwtIG1vbnRoKHRlc3REYXRlblNhdHokRGF0dW0pCnRlc3REYXRlblNhdHokZGF5IDwtIGRheSh0ZXN0RGF0ZW5TYXR6JERhdHVtKQoKdGVzdERhdGVuU2F0eiREYXR1bSA8LSBOVUxMCgpzdW1tYXJ5KHRlc3REYXRlblNhdHopCnNhdmUodGVzdERhdGVuU2F0eiwgZmlsZT0iRGF0ZW5hdWZiZXJlaXR1bmdfVGVzdGRhdGVuLlJkYSIpCmBgYAoKIyMjIEZlYXR1cmVzICYgTGFiZWxzCgpgYGB7cn0KZmVhdHVyZXMgPC0gYygiZGF5IiwgICAgICAgICAgICAgICAgICAgICAgICAgICAibW9udGgiLCAgICAgICAgICAgICAgICAgICAgICAgICAieWVhciIsCiAgICAgICAgICAgICAgIldpbmRzdGFlcmtlIiwgICAgICAgICAgICAgICAgICAgIlRlbXBlcmF0dXIiLCAgICAgICAgICAgICAgICAgICAgIldDX0Jld8O2bGt1bmdfYWJuZWhtZW5kIiwKICAgICAgICAgICAgICAiV0NfQmV3w7Zsa3VuZ19nbGVpY2hibGVpYmVuZCIsICAgIldDX0Jld8O2bGt1bmdfbmljaHRfYmVvYmFjaHRldCIsICJXQ19CZXfDtmxrdW5nX3p1bmVobWVuZCIsCiAgICAgICAgICAgICAgIldDX0R1bnN0X1N0YXViIiwgICAgICAgICAgICAgICAgIldDX0VyZWlnbmlzc2VfbGV0enRlX2giLCAgICAgICAgIldDX0dld2l0dGVyIiwKICAgICAgICAgICAgICAiV0NfTmViZWxfRWlzbmViZWwiLCAgICAgICAgICAgICAiV0NfUmVnZW4iLCAgICAgICAgICAgICAgICAgICAgICAiV0NfU2NoYXVlciIsCiAgICAgICAgICAgICAgIldDX1NjaG5lZSIsICAgICAgICAgICAgICAgICAgICAgIldDX1NwcsO8aHJlZ2VuIiwgICAgICAgICAgICAgICAgICJXQ19Ucm9ja2VuZXJlaWduaXNzZSIsCiAgICAgICAgICAgICAgIldDX05BIiwgICAgICAgICAgICAgICAgICAgICAgICAgIkJld29lbGt1bmdzZ3JhZF9nZXJpbmciLCAgICAgICAgIkJld29lbGt1bmdzZ3JhZF9rZWluZSIsCiAgICAgICAgICAgICAgIkJld29lbGt1bmdzZ3JhZF9taXR0ZWwiLCAgICAgICAgIkJld29lbGt1bmdzZ3JhZF9zdGFyayIsICAgICAgICAgIkJld29lbGt1bmdzZ3JhZF9OQSIsCiAgICAgICAgICAgICAgIlNjaHVsZmVyaWVuIiwgICAgICAgICAgICAgICAgICAgIktpZWxlcldvY2hlIiwgICAgICAgICAgICAgICAgICAgIldvY2hlbnRhZ19UdWVzZGF5IiwKICAgICAgICAgICAgICAiV29jaGVudGFnX1RodXJzZGF5IiwgICAgICAgICAgICAiU2Fpc29uYnJvdCIsICAgICAgICAgICAgICAgICAgICAiVW1zYXR6IiwKICAgICAgICAgICAgICAiV29jaGVudGFnX0ZyaWRheSIsICAgICAgICAgICAgICAiV29jaGVudGFnX1dlZG5lc2RheSIsICAgICAgICAgICAiV29jaGVudGFnX01vbmRheSIsCiAgICAgICAgICAgICAgIldvY2hlbnRhZ19TYXR1cmRheSIsICAgICAgICAgICAgIldvY2hlbnRhZ19TdW5kYXkiCiAgICAgICAgICAgICAgKQoKbGFiZWxzIDwtIGMoIkJyb3QiLCAiQnLDtnRjaGVuIiwgIkNyb2lzc2FudCIsICJLb25kaXRvcmVpIiwgIkt1Y2hlbiIpCmBgYAoKIyMjIFNlbGVjdGlvbiBvZiBUcmFpbmluZywgVmFsaWRhdGlvbiBhbmQgVGVzdCBEYXRhCgpgYGB7cn0KIyBTZXR0aW5nIHRoZSByYW5kb20gY291bnRlciB0byBhIGZpeGVkIHZhbHVlLCBzbyB0aGUgcmFuZG9tIGluaXRpYWxpemF0aW9uIHN0YXlzIHRoZSBzYW1lICh0aGUgcmFuZG9tIHNwbGl0IGlzIGFsd2F5cyB0aGUgc2FtZSkKc2V0LnNlZWQoMSkKCmFzc2lnbm1lbnQgPC0gc2FtcGxlKDE6Miwgc2l6ZSA9IG5yb3coYWxsRGF0YV9kdW1teSksIHByb2IgPSBjKC44LCAuMiksIHJlcGxhY2UgPSBUUlVFKQphbGxEYXRhX2R1bW15MiA8LSByYmluZChhbGxEYXRhX2R1bW15W2Fzc2lnbm1lbnQgPT0gMSxdLCBzeW50aHBvcF9hbGxEYXRhX2R1bW15KQoKIyBDcmVhdGUgdHJhaW5pbmcsIHZhbGlkYXRpb24gYW5kIHRlc3QgZGF0YSBmb3IgdGhlIGZlYXR1cmVzIGFuZCB0aGUgbGFiZWxzCnRyYWluaW5nX2ZlYXR1cmVzIDwtIGFsbERhdGFfZHVtbXkyWyxmZWF0dXJlc10jW2Fzc2lnbm1lbnQgPT0gMSwgZmVhdHVyZXNdICAgIAp0cmFpbmluZ19sYWJlbHMgPC0gYWxsRGF0YV9kdW1teTJbLGxhYmVsc10jW2Fzc2lnbm1lbnQgPT0gMSwgbGFiZWxzXSAgICAKdHJhaW5pbmdfbGFiZWxzIDwtIGFzLmRhdGEuZnJhbWUodHJhaW5pbmdfbGFiZWxzKQoKdmFsaWRhdGlvbl9mZWF0dXJlcyA8LSBhbGxEYXRhX2R1bW15W2Fzc2lnbm1lbnQgPT0gMiwgZmVhdHVyZXNdICAKdmFsaWRhdGlvbl9sYWJlbHMgPC0gYWxsRGF0YV9kdW1teVthc3NpZ25tZW50ID09IDIsIGxhYmVsc10gIAp2YWxpZGF0aW9uX2xhYmVscyA8LSBhcy5kYXRhLmZyYW1lKHZhbGlkYXRpb25fbGFiZWxzKQoKdGVzdGluZ19mZWF0dXJlcyA8LSB0ZXN0RGF0ZW5TYXR6ICU+JSBzZWxlY3QoYWxsX29mKGZlYXR1cmVzKSkKCiNhcmUgdGhlcmUgYW55IG1pc3NpbmcgdmFsdWVzPwp0YWJsZShpcy5uYSh0cmFpbmluZ19mZWF0dXJlcykpCnRhYmxlKGlzLm5hKHZhbGlkYXRpb25fZmVhdHVyZXMpKQp0YWJsZShpcy5uYSh0ZXN0aW5nX2ZlYXR1cmVzKSkKI3N1bW1hcnkoYWxsRGF0YV9kdW1teSkKYGBgCgojIyMgTW9kZWxsIGF1ZnN0ZWxsZW4gaW4gUHl0aG9uCgpgYGB7cHl0aG9ufQppbXBvcnQgbnVtcHkgYXMgbnAKaW1wb3J0IHRlbnNvcmZsb3cgYXMgdGYKZnJvbSB0ZW5zb3JmbG93LmtlcmFzLm1vZGVscyBpbXBvcnQgU2VxdWVudGlhbApmcm9tIHRlbnNvcmZsb3cua2VyYXMubGF5ZXJzIGltcG9ydCBJbnB1dExheWVyLCBEZW5zZSwgQmF0Y2hOb3JtYWxpemF0aW9uLCBEcm9wb3V0CmZyb20gdGVuc29yZmxvdy5rZXJhcy5vcHRpbWl6ZXJzIGltcG9ydCBBZGFtCgojIFRoZSBhcmd1bWVudCAiaW5wdXRfc2hhcGUiIGZvciB0aGUgZGVmaW5pdGlvbiBvZiB0aGUgaW5wdXQgbGF5ZXIgbXVzdCBpbmNsdWRlIAojIHRoZSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzIChmZWF0dXJlcykgdXNlZCBmb3IgdGhlIG1vZGVsLiAKIyBUbyBhdXRvbWF0aWNhbGx5IGNhbGN1bGF0ZSB0aGlzIG51bWJlciB3ZSB1c2UgdGhlIGZ1bmN0aW9uIGByLnRyYWluaW5nX2ZlYXR1cmVzLmtleXMoKWAsIAojIHdoaWNoIHJldHVybnMgdGhlIGxpc3Qgb2YgdmFyaWFibGUgbmFtZXMgb2YgdGhlIGRhdGFmcmFtZSBgdHJhaW5pbmdfZmVhdHVyZXNgLgojIFRoZW4sIHRoZSBmdW50aW9uIGBsZW4oKWAgcmV0dXJucyB0aGUgbGVuZ3RoIG9mIHRoaXMgbGlzdCBvZiB2YXJpYWJsZSBuYW1lcyAKIyAoaS5lLiB0aGUgbnVtYmVyIG9mIHZhcmlhYmxlcyBpbiB0aGUgaW5wdXQpCgptb2RlbCA9IFNlcXVlbnRpYWwoWwogIElucHV0TGF5ZXIoaW5wdXRfc2hhcGUgPSAobGVuKHIudHJhaW5pbmdfZmVhdHVyZXMua2V5cygpKSwgKSksCiAgQmF0Y2hOb3JtYWxpemF0aW9uKCksCiAgRGVuc2UobGVuKHIudHJhaW5pbmdfZmVhdHVyZXMua2V5cygpKSwgYWN0aXZhdGlvbiA9ICdzd2lzaCcpLAogIERyb3BvdXQoMC4yKSwKICBEZW5zZShsZW4oci50cmFpbmluZ19mZWF0dXJlcy5rZXlzKCkpLCBhY3RpdmF0aW9uID0gJ3N3aXNoJyksCiAgRHJvcG91dCgwLjIpLAogIERlbnNlKGxlbihyLnRyYWluaW5nX2ZlYXR1cmVzLmtleXMoKSksIGFjdGl2YXRpb24gPSAnc3dpc2gnKSwKICBEcm9wb3V0KDAuMiksCiAgRGVuc2UobGVuKHIudHJhaW5pbmdfZmVhdHVyZXMua2V5cygpKSwgYWN0aXZhdGlvbiA9ICdzd2lzaCcpLAogIERlbnNlKDUpCl0pCgojIEF1c2dhYmUgZWluZXIgWlVzYW1tZW5mYXNzdW5nIHp1ciBGb3JtIGRlcyBNT2RlbGxzLCBkYXMgZ2VzY2jDpHR6dCB3aXJkIChuaWNodCBub3R3ZW5kaWcpCiNtb2RlbC5zdW1tYXJ5KCkKYGBgCgojIyMgU2Now6R0enVuZyBkZSBuZXVyb25hbGVuIE5ldHplcwoKYGBge3B5dGhvbn0KIyBkZWZpbml0aW9uIG9mIHRoZSBsb3NzIGZ1bmN0aW9uIGFuZCB0aGUgb3B0aW1hemF0aW9uIGZ1bmN0aW9uIHdpdGggaHlwZXJwYXJhbWV0ZXJzCm1vZGVsLmNvbXBpbGUobG9zcz0ibWFwZSIsIG9wdGltaXplcj1BZGFtKGxlYXJuaW5nX3JhdGU9MC4wMDEpKQoKI1NjaMOkdHp1bmcgZGVzIE1vZGVsbHMKaGlzdG9yeSA9IG1vZGVsLmZpdChyLnRyYWluaW5nX2ZlYXR1cmVzLCByLnRyYWluaW5nX2xhYmVscywgZXBvY2hzID0gNzUwLAogICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZGF0YSA9IChyLnZhbGlkYXRpb25fZmVhdHVyZXMsIHIudmFsaWRhdGlvbl9sYWJlbHMpLCB2ZXJib3NlID0gMCkKCm1vZGVsLnNhdmUoInB5dGhvbl9tb2RlbC5oNSIpCmBgYAoKIyMjIGdyYXBoaXNjaGUgQXVzZ2FiZSBkZXIgTW9kZWxsb3B0aW1pZXJ1bmcKCmBgYHtyfQojIEdyYXBoaXNjaGUgQXVzZ2FiZSBkZXIgTW9kZWxsb3B0aW1pZXJ1bmcKCiNjcmVhdGUgZGF0YQpkYXRhIDwtIGRhdGEuZnJhbWUodmFsX2xvc3MgPSB1bmxpc3QocHkkaGlzdG9yeSRoaXN0b3J5JHZhbF9sb3NzKSwKICAgICAgICAgICAgICAgICAgIGxvc3MgPSB1bmxpc3QocHkkaGlzdG9yeSRoaXN0b3J5JGxvc3MpKQoKZ2dwbG90KGRhdGFbLSgxOjEwKSwgXSkrCiAgZ2VvbV9saW5lKGFlcyh4ID0gMTpsZW5ndGgodmFsX2xvc3MpLCB5ID0gdmFsX2xvc3MsIGNvbG91ciA9ICJWYWxpZGF0aW9uIExvc3MiKSkgKwogIGdlb21fbGluZShhZXMoeCA9IDE6bGVuZ3RoKGxvc3MpLCB5ID0gbG9zcywgY29sb3VyID0gIlRyYWluaW5nIExvc3MiKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiVHJhaW5pbmcgTG9zcyI9ImJsdWUiLCAiVmFsaWRhdGlvbiBMb3NzIiA9ICJyZWQiKSkgKwogIGxhYnModGl0bGUgPSAiTG9zcyBGdW5jdGlvbiBWYWx1ZXMgRHVyaW5nIE9wdGltYXphdGlvbiIpICsKICB4bGFiKCJJdGVyYXRpb24gTnVtYmVyIikgKwogIHlsYWIoIkxvc3MiKQpgYGAKCiMjIyBBdXN3ZXJ0dW5nIGRlciBTY2jDpHR6ZXJnZWJuaXNzZQoKYGBge3J9CiMgU2Now6R0enVuZyBkZXIgKG5vcm1pZXJ0ZW4pIFByZWlzZSBmw7xyIGRpZSBUcmFpbmluZ3MtIHVuZCBUZXN0ZGF0ZW4KdHJhaW5pbmdfcHJlZGljdGlvbnMgPC0gcHkkbW9kZWwkcHJlZGljdCh0cmFpbmluZ19mZWF0dXJlcykKdmFsaWRhdGlvbl9wcmVkaWN0aW9ucyA8LSBweSRtb2RlbCRwcmVkaWN0KHZhbGlkYXRpb25fZmVhdHVyZXMpCnRlc3RpbmdfcHJlZGljdGlvbnMgPC0gcHkkbW9kZWwkcHJlZGljdCh0ZXN0aW5nX2ZlYXR1cmVzKQoKIyBWZXJnbGVpY2ggZGVyIEfDvHRla3JpdGVyaWVuIGbDvHIgZGllIFRyYWluZ2luZ3MtIHVuZCBUZXN0ZGF0ZW4KYSA8LSBmb3JtYXQobWFwZSh0cmFpbmluZ19sYWJlbHNbLDFdLCB0cmFpbmluZ19wcmVkaWN0aW9uc1ssMV0pKjEwMCwgZGlnaXRzPTMsIG5zbWFsbD0yKQpiIDwtIGZvcm1hdChtYXBlKHRyYWluaW5nX2xhYmVsc1ssMl0sIHRyYWluaW5nX3ByZWRpY3Rpb25zWywyXSkqMTAwLCBkaWdpdHM9MywgbnNtYWxsPTIpCmMgPC0gZm9ybWF0KG1hcGUodHJhaW5pbmdfbGFiZWxzWywzXSwgdHJhaW5pbmdfcHJlZGljdGlvbnNbLDNdKSoxMDAsIGRpZ2l0cz0zLCBuc21hbGw9MikKZCA8LSBmb3JtYXQobWFwZSh0cmFpbmluZ19sYWJlbHNbLDRdLCB0cmFpbmluZ19wcmVkaWN0aW9uc1ssNF0pKjEwMCwgZGlnaXRzPTMsIG5zbWFsbD0yKQplIDwtIGZvcm1hdChtYXBlKHRyYWluaW5nX2xhYmVsc1ssNV0sIHRyYWluaW5nX3ByZWRpY3Rpb25zWyw1XSkqMTAwLCBkaWdpdHM9MywgbnNtYWxsPTIpCgpjYXQocGFzdGUwKCJcbk1BUEUgb24gdGhlIFRyYWluaW5nIERhdGExOlx0IiwgYSkpCmNhdChwYXN0ZTAoIlxuTUFQRSBvbiB0aGUgVHJhaW5pbmcgRGF0YTI6XHQiLCBiKSkKY2F0KHBhc3RlMCgiXG5NQVBFIG9uIHRoZSBUcmFpbmluZyBEYXRhMzpcdCIsIGMpKQpjYXQocGFzdGUwKCJcbk1BUEUgb24gdGhlIFRyYWluaW5nIERhdGE0Olx0IiwgZCkpCmNhdChwYXN0ZTAoIlxuTUFQRSBvbiB0aGUgVHJhaW5pbmcgRGF0YTU6XHQiLCBlLCAiXG4iKSkKCmcgPC0gZm9ybWF0KG1hcGUodmFsaWRhdGlvbl9sYWJlbHNbLDFdLCB2YWxpZGF0aW9uX3ByZWRpY3Rpb25zWywxXSkqMTAwLCBkaWdpdHM9MywgbnNtYWxsPTIpCmggPC0gZm9ybWF0KG1hcGUodmFsaWRhdGlvbl9sYWJlbHNbLDJdLCB2YWxpZGF0aW9uX3ByZWRpY3Rpb25zWywyXSkqMTAwLCBkaWdpdHM9MywgbnNtYWxsPTIpCmkgPC0gZm9ybWF0KG1hcGUodmFsaWRhdGlvbl9sYWJlbHNbLDNdLCB2YWxpZGF0aW9uX3ByZWRpY3Rpb25zWywzXSkqMTAwLCBkaWdpdHM9MywgbnNtYWxsPTIpCmogPC0gZm9ybWF0KG1hcGUodmFsaWRhdGlvbl9sYWJlbHNbLDRdLCB2YWxpZGF0aW9uX3ByZWRpY3Rpb25zWyw0XSkqMTAwLCBkaWdpdHM9MywgbnNtYWxsPTIpCmsgPC0gZm9ybWF0KG1hcGUodmFsaWRhdGlvbl9sYWJlbHNbLDVdLCB2YWxpZGF0aW9uX3ByZWRpY3Rpb25zWyw1XSkqMTAwLCBkaWdpdHM9MywgbnNtYWxsPTIpCiAgCmNhdChwYXN0ZTAoIlxuTUFQRSBvbiB0aGUgVmFsaWRhdGlvbiBEYXRhMTpcdCIsIGcpKQpjYXQocGFzdGUwKCJcbk1BUEUgb24gdGhlIFZhbGlkYXRpb24gRGF0YTI6XHQiLCBoKSkKY2F0KHBhc3RlMCgiXG5NQVBFIG9uIHRoZSBWYWxpZGF0aW9uIERhdGEzOlx0IiwgaSkpCmNhdChwYXN0ZTAoIlxuTUFQRSBvbiB0aGUgVmFsaWRhdGlvbiBEYXRhNDpcdCIsIGopKQpjYXQocGFzdGUwKCJcbk1BUEUgb24gdGhlIFZhbGlkYXRpb24gRGF0YTU6XHQiLCBrLCAiXG4iKSkKCiMgTWVhbiBvZiBUcmFpbmluZyBhbmQgVmFsaWRhdGlvbiBEYXRhIE1BUEUKbWVhblQgPC0gYyhhcy5kb3VibGUoYSksIGFzLmRvdWJsZShiKSwgYXMuZG91YmxlKGMpLCBhcy5kb3VibGUoZCksIGFzLmRvdWJsZShlKSkgCm1lYW5WIDwtIGMoYXMuZG91YmxlKGcpLCBhcy5kb3VibGUoaCksIGFzLmRvdWJsZShpKSwgYXMuZG91YmxlKGopLCBhcy5kb3VibGUoaykpIAoKY2F0KHBhc3RlMCgiXG5NZWFuIFRyYWluaW5nIE1BUEU6ICIsIG1lYW4obWVhblQpLCAiXG4iKSkKY2F0KHBhc3RlMCgiTWVhbiBWYWxpZGF0aW9uIE1BUEU6ICIsIG1lYW4obWVhblYpLCAiXG4iKSkKYGBgCgojIyMgR3JhZmlzY2hlciB2ZXJnbGVpY2ggZGVyIHZvcmhlcmdlc2FndGVuICYgdGF0c8OkY2hsaWNoZXIgUHJlaXNlIGbDvHIgZGllIFRyYWluaW5ncy0gdW5kIFZhbGlkaWVydW5nc2RhdGVuCgpgYGB7cn0KZGF0YV90cmFpbiA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB0cmFpbmluZ19wcmVkaWN0aW9uc1ssMV0sIGFjdHVhbCA9IHRyYWluaW5nX2xhYmVsc1ssMV0pCmRhdGFfdmFsIDwtIGRhdGEuZnJhbWUocHJlZGljdGlvbiA9IHZhbGlkYXRpb25fcHJlZGljdGlvbnNbLDFdLCBhY3R1YWwgPSB2YWxpZGF0aW9uX2xhYmVsc1ssMV0pCmRhdGFfdGVzdCA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB0ZXN0aW5nX3ByZWRpY3Rpb25zWywxXSkKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVHJhaW5pbmdzZGF0ZW4KZ2dwbG90KGRhdGFfdHJhaW5bXSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKGFjdHVhbCksIHk9YWN0dWFsLCBjb2xvdXIgPSAiQWN0dWFsIFZhbHVlcyIgKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwoIHZhbHVlcyA9IGMoIlByZWRpY3RlZCBWYWx1ZXMiPSJibHVlIiwgIkFjdHVhbCBWYWx1ZXMiPSJyZWQiKSApICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgYW5kIEFjdHVhbCBWYWx1ZXMgZm9yIHRoZSBUcmFpbmluZyBEYXRhIDEiKSArCiAgeGxhYigiQ2FzZSBOdW1iZXIiKSArCiAgeWxhYigiUHJpY2UgaW4gRVVSIikgCgojIFBsb3QgZGVyIEVyZ2Vibmlzc2UgZGVyIFZhbGlkaWVydW5nc2RhdGVuCmdncGxvdChkYXRhX3ZhbFssXSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKGFjdHVhbCksIHk9YWN0dWFsLCBjb2xvdXIgPSAiQWN0dWFsIFZhbHVlcyIgKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwoIHZhbHVlcyA9IGMoIlByZWRpY3RlZCBWYWx1ZXMiPSJibHVlIiwgIkFjdHVhbCBWYWx1ZXMiPSJyZWQiKSApICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgYW5kIEFjdHVhbCBWYWx1ZXMgZm9yIHRoZSBWYWxpZGF0aW9uIERhdGEgMSIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKQoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBUZXN0ZGF0ZW4KZ2dwbG90KGRhdGFfdGVzdCkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0aW9uIGZvciB0aGUgVGVzdCBEYXRhIDEiKSArCiAgeGxhYigiQ2FzZSBOdW1iZXIiKSArCiAgeWxhYigiUHJpY2UgaW4gRVVSIikgCgojLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAyIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0jCgpkYXRhX3RyYWluMiA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB0cmFpbmluZ19wcmVkaWN0aW9uc1ssMl0sIGFjdHVhbCA9IHRyYWluaW5nX2xhYmVsc1ssMl0pCmRhdGFfdmFsMiA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB2YWxpZGF0aW9uX3ByZWRpY3Rpb25zWywyXSwgYWN0dWFsID0gdmFsaWRhdGlvbl9sYWJlbHNbLDJdKQpkYXRhX3Rlc3QyIDwtIGRhdGEuZnJhbWUocHJlZGljdGlvbiA9IHRlc3RpbmdfcHJlZGljdGlvbnNbLDJdKQoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBUcmFpbmluZ3NkYXRlbgpnZ3Bsb3QoZGF0YV90cmFpbjIpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChhY3R1YWwpLCB5PWFjdHVhbCwgY29sb3VyID0gIkFjdHVhbCBWYWx1ZXMiICkpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKCB2YWx1ZXMgPSBjKCJQcmVkaWN0ZWQgVmFsdWVzIj0iYmx1ZSIsICJBY3R1YWwgVmFsdWVzIj0icmVkIikgKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIGFuZCBBY3R1YWwgVmFsdWVzIGZvciB0aGUgVHJhaW5pbmcgRGF0YSAyIikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpIAoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBWYWxpZGllcnVuZ3NkYXRlbgpnZ3Bsb3QoZGF0YV92YWwyKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChwcmVkaWN0aW9uKSwgeT1wcmVkaWN0aW9uLCBjb2xvdXIgPSAiUHJlZGljdGVkIFZhbHVlcyIgKSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgoYWN0dWFsKSwgeT1hY3R1YWwsIGNvbG91ciA9ICJBY3R1YWwgVmFsdWVzIiApKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCggdmFsdWVzID0gYygiUHJlZGljdGVkIFZhbHVlcyI9ImJsdWUiLCAiQWN0dWFsIFZhbHVlcyI9InJlZCIpICkgKwogIGxhYnModGl0bGU9IlByZWRpY3RlZCBhbmQgQWN0dWFsIFZhbHVlcyBmb3IgdGhlIFZhbGlkYXRpb24gRGF0YSAyIikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpCgojIFBsb3QgZGVyIEVyZ2Vibmlzc2UgZGVyIFRlc3RkYXRlbgpnZ3Bsb3QoZGF0YV90ZXN0MikgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0aW9uIGZvciB0aGUgVGVzdCBEYXRhIDIiKSArCiAgeGxhYigiQ2FzZSBOdW1iZXIiKSArCiAgeWxhYigiUHJpY2UgaW4gRVVSIikgCgojLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0jCgpkYXRhX3RyYWluMyA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB0cmFpbmluZ19wcmVkaWN0aW9uc1ssM10sIGFjdHVhbCA9IHRyYWluaW5nX2xhYmVsc1ssM10pCmRhdGFfdmFsMyA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB2YWxpZGF0aW9uX3ByZWRpY3Rpb25zWywzXSwgYWN0dWFsID0gdmFsaWRhdGlvbl9sYWJlbHNbLDNdKQpkYXRhX3Rlc3QzIDwtIGRhdGEuZnJhbWUocHJlZGljdGlvbiA9IHRlc3RpbmdfcHJlZGljdGlvbnNbLDNdKQoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBUcmFpbmluZ3NkYXRlbgpnZ3Bsb3QoZGF0YV90cmFpbjMpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChhY3R1YWwpLCB5PWFjdHVhbCwgY29sb3VyID0gIkFjdHVhbCBWYWx1ZXMiICkpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKCB2YWx1ZXMgPSBjKCJQcmVkaWN0ZWQgVmFsdWVzIj0iYmx1ZSIsICJBY3R1YWwgVmFsdWVzIj0icmVkIikgKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIGFuZCBBY3R1YWwgVmFsdWVzIGZvciB0aGUgVHJhaW5pbmcgRGF0YSAzIikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpIAoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBWYWxpZGllcnVuZ3NkYXRlbgpnZ3Bsb3QoZGF0YV92YWwzKSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChwcmVkaWN0aW9uKSwgeT1wcmVkaWN0aW9uLCBjb2xvdXIgPSAiUHJlZGljdGVkIFZhbHVlcyIgKSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgoYWN0dWFsKSwgeT1hY3R1YWwsIGNvbG91ciA9ICJBY3R1YWwgVmFsdWVzIiApKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCggdmFsdWVzID0gYygiUHJlZGljdGVkIFZhbHVlcyI9ImJsdWUiLCAiQWN0dWFsIFZhbHVlcyI9InJlZCIpICkgKwogIGxhYnModGl0bGU9IlByZWRpY3RlZCBhbmQgQWN0dWFsIFZhbHVlcyBmb3IgdGhlIFZhbGlkYXRpb24gRGF0YSAzIikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpCgojIFBsb3QgZGVyIEVyZ2Vibmlzc2UgZGVyIFRlc3RkYXRlbgpnZ3Bsb3QoZGF0YV90ZXN0MykgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0aW9uIGZvciB0aGUgVGVzdCBEYXRhIDMiKSArCiAgeGxhYigiQ2FzZSBOdW1iZXIiKSArCiAgeWxhYigiUHJpY2UgaW4gRVVSIikgCgoKIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gNCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIwoKZGF0YV90cmFpbjQgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdHJhaW5pbmdfcHJlZGljdGlvbnNbLDRdLCBhY3R1YWwgPSB0cmFpbmluZ19sYWJlbHNbLDRdKQpkYXRhX3ZhbDQgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdmFsaWRhdGlvbl9wcmVkaWN0aW9uc1ssNF0sIGFjdHVhbCA9IHZhbGlkYXRpb25fbGFiZWxzWyw0XSkKZGF0YV90ZXN0NCA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB0ZXN0aW5nX3ByZWRpY3Rpb25zWyw0XSkKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVHJhaW5pbmdzZGF0ZW4KZ2dwbG90KGRhdGFfdHJhaW40KSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChwcmVkaWN0aW9uKSwgeT1wcmVkaWN0aW9uLCBjb2xvdXIgPSAiUHJlZGljdGVkIFZhbHVlcyIgKSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgoYWN0dWFsKSwgeT1hY3R1YWwsIGNvbG91ciA9ICJBY3R1YWwgVmFsdWVzIiApKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCggdmFsdWVzID0gYygiUHJlZGljdGVkIFZhbHVlcyI9ImJsdWUiLCAiQWN0dWFsIFZhbHVlcyI9InJlZCIpICkgKwogIGxhYnModGl0bGU9IlByZWRpY3RlZCBhbmQgQWN0dWFsIFZhbHVlcyBmb3IgdGhlIFRyYWluaW5nIERhdGEgNCIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKSAKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVmFsaWRpZXJ1bmdzZGF0ZW4KZ2dwbG90KGRhdGFfdmFsNCkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKGFjdHVhbCksIHk9YWN0dWFsLCBjb2xvdXIgPSAiQWN0dWFsIFZhbHVlcyIgKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwoIHZhbHVlcyA9IGMoIlByZWRpY3RlZCBWYWx1ZXMiPSJibHVlIiwgIkFjdHVhbCBWYWx1ZXMiPSJyZWQiKSApICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgYW5kIEFjdHVhbCBWYWx1ZXMgZm9yIHRoZSBWYWxpZGF0aW9uIERhdGEgNCIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKQoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBUZXN0ZGF0ZW4KZ2dwbG90KGRhdGFfdGVzdDQpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGlvbiBmb3IgdGhlIFRlc3QgRGF0YSA0IikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpIAoKIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gNSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIwoKZGF0YV90cmFpbjUgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdHJhaW5pbmdfcHJlZGljdGlvbnNbLDVdLCBhY3R1YWwgPSB0cmFpbmluZ19sYWJlbHNbLDVdKQpkYXRhX3ZhbDUgPC0gZGF0YS5mcmFtZShwcmVkaWN0aW9uID0gdmFsaWRhdGlvbl9wcmVkaWN0aW9uc1ssNV0sIGFjdHVhbCA9IHZhbGlkYXRpb25fbGFiZWxzWyw1XSkKZGF0YV90ZXN0NSA8LSBkYXRhLmZyYW1lKHByZWRpY3Rpb24gPSB0ZXN0aW5nX3ByZWRpY3Rpb25zWyw1XSkKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVHJhaW5pbmdzZGF0ZW4KZ2dwbG90KGRhdGFfdHJhaW41KSArCiAgZ2VvbV9saW5lKCBhZXMoeD0xOmxlbmd0aChwcmVkaWN0aW9uKSwgeT1wcmVkaWN0aW9uLCBjb2xvdXIgPSAiUHJlZGljdGVkIFZhbHVlcyIgKSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgoYWN0dWFsKSwgeT1hY3R1YWwsIGNvbG91ciA9ICJBY3R1YWwgVmFsdWVzIiApKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCggdmFsdWVzID0gYygiUHJlZGljdGVkIFZhbHVlcyI9ImJsdWUiLCAiQWN0dWFsIFZhbHVlcyI9InJlZCIpICkgKwogIGxhYnModGl0bGU9IlByZWRpY3RlZCBhbmQgQWN0dWFsIFZhbHVlcyBmb3IgdGhlIFRyYWluaW5nIERhdGEgNSIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKSAKCiMgUGxvdCBkZXIgRXJnZWJuaXNzZSBkZXIgVmFsaWRpZXJ1bmdzZGF0ZW4KZ2dwbG90KGRhdGFfdmFsNSkgKwogIGdlb21fbGluZSggYWVzKHg9MTpsZW5ndGgocHJlZGljdGlvbiksIHk9cHJlZGljdGlvbiwgY29sb3VyID0gIlByZWRpY3RlZCBWYWx1ZXMiICkpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKGFjdHVhbCksIHk9YWN0dWFsLCBjb2xvdXIgPSAiQWN0dWFsIFZhbHVlcyIgKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwoIHZhbHVlcyA9IGMoIlByZWRpY3RlZCBWYWx1ZXMiPSJibHVlIiwgIkFjdHVhbCBWYWx1ZXMiPSJyZWQiKSApICsKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgYW5kIEFjdHVhbCBWYWx1ZXMgZm9yIHRoZSBWYWxpZGF0aW9uIERhdGEgNSIpICsKICB4bGFiKCJDYXNlIE51bWJlciIpICsKICB5bGFiKCJQcmljZSBpbiBFVVIiKQoKIyBQbG90IGRlciBFcmdlYm5pc3NlIGRlciBUZXN0ZGF0ZW4KZ2dwbG90KGRhdGFfdGVzdDUpICsKICBnZW9tX2xpbmUoIGFlcyh4PTE6bGVuZ3RoKHByZWRpY3Rpb24pLCB5PXByZWRpY3Rpb24sIGNvbG91ciA9ICJQcmVkaWN0ZWQgVmFsdWVzIiApKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGlvbiBmb3IgdGhlIFRlc3QgRGF0YSA1IikgKwogIHhsYWIoIkNhc2UgTnVtYmVyIikgKwogIHlsYWIoIlByaWNlIGluIEVVUiIpIApgYGAKCmBgYHtyfQoKYGBgCg==